package server;

import DM.DialogManager;
import DM.StateMachine;
import config.MongoDB;
import entities.SLUResult;
import entities.StateMachineModel;
import entities.SysAction;
import gate.*;
import gate.Document;
import gate.corpora.DocumentContentImpl;
import gate.creole.ExecutionException;
import gate.creole.ResourceInstantiationException;
import gate.creole.SerialAnalyserController;
import gate.persist.PersistenceException;
import gate.util.GateException;
import org.ansj.domain.Result;
import org.apache.log4j.Logger;
import sluentities.ClassifierResult;

import java.io.*;
import java.net.URL;
import java.util.*;

/**
 * Created by sunjia on 2017/4/1.
 */
public class Parser {

    private SerialAnalyserController analyser;
    private DialogManager dialogManager;
    private MaxEntClassifier classifier;

    ArrayList<SLUResult> currentSLU;//Gate生成结果as,经过过滤和筛选
    SysAction currentAction = null;//对话器返回结果ac
    String conflictReply = "";
    Set<Annotation> currentGaz = new HashSet<>();

    DefaultFunction defaultFunction;

    OutTermProcesser processer = new OutTermProcesser();

    Corpus corpus = null;
    gate.Document gateDoc = null;

    private static Logger logger = Logger.getLogger(ParseServer.class);

    public Parser(URL appURL){
        try {
            analyser =
                    (SerialAnalyserController)gate.util.persistence.PersistenceManager.loadObjectFromUrl(appURL);

            defaultFunction = new DefaultFunction(analyser);

            corpus = Factory.newCorpus("BatchProcessApp Corpus");
            gateDoc = Factory.newDocument("");
            gateDoc.setParameterValue("encoding", "UTF-8");
            corpus.add(gateDoc);
            analyser.setCorpus(corpus);

            classifier = new MaxEntClassifier();


        } catch (PersistenceException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ResourceInstantiationException e) {
            e.printStackTrace();
        }
    }

    public Set<Annotation> getCurrentGaz() {
        return currentGaz;
    }

    private void getGazetteer(Document doc)
    {
        currentGaz = new HashSet<>();
        AnnotationSet annotations = doc.getAnnotations("Gazetteer");
        for (Annotation as:annotations){
            if(!as.getType().equals("Token")){
                currentGaz.add(as);
            }
        }
    }

    public SysAction greeting(String userid){

        currentAction = dialogManager.getFirstQusetion(userid);
        return currentAction;
    }

    public Map<String,Double> outOfVacProcess(String input) throws Exception {

        Map<String,Double> ruleNames = processer.search(input, 0.95f);
        return ruleNames;
    }


    public ArrayList<SLUResult> gateAnalyser(String input) throws Exception {

        gateDoc.setContent(new DocumentContentImpl(input));
        analyser.execute();
        AnnotationSet as = gateDoc.getAnnotations("Candidates");
        getGazetteer(gateDoc);
        ArrayList<SLUResult>  gateResults =  getSLUResult(as);
        //若有多个均是##下的slot时，应合并在一起
        Map<String,String> slotsCom = new HashMap<>();
        for (SLUResult res : gateResults) {
            for (String key : res.slots.keySet()){
                slotsCom.put(key, res.slots.get(key));
/*                int matchLen =  res.slots.get(key).length();
                if (key.contains("password")) {
                    if (((input.length()==matchLen) && input.length()==1) //单独#的情况
                            || ((input.length() == (matchLen + 1)) && input.length()==7)) {//6位密码+h
                        slotsCom.put(key, res.slots.get(key));
                    }
                }else if(key.contains("idCard")){
                    if (((input.length()==matchLen) && input.length()==1) //单独#的情况
                            || ((input.length() == (matchLen + 1)) && input.length()==19))  {//18位身份证+h
                        slotsCom.put(key, res.slots.get(key));
                    }
                }else{
                    slotsCom.put(key, res.slots.get(key));
                }*/
            }
        }
        ArrayList<SLUResult> gateResultsCom = new ArrayList<>();
        if (slotsCom.size() > 0) {
            gateResultsCom.add(new SLUResult("##", "stateNode", slotsCom.size(), slotsCom));
        }
        return gateResultsCom;
    }

    //预处理：替换同义词
    public String preprocess(String line) throws Exception {

        String inputPrepro = "";
        String path_tongyi = Parser.class.getClassLoader().getResource("tongyin.dic").getPath();;
        InputStreamReader inputStreamReader = new InputStreamReader(new FileInputStream(path_tongyi), "utf-8");
        BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
        String word = "";
        while ((word = bufferedReader.readLine()) != null) {
            String tar = word.trim().split("\t")[0];
            String replacement = word.trim().split("\t")[1];
            inputPrepro = line.replace(tar,replacement);
        }
        return inputPrepro;
    }

    //组合：编辑距离/分类器和Gate的结果
    public ArrayList<SLUResult> combineResult(Map<String,Double> resultMap, ArrayList<SLUResult> gateResult) {

        ArrayList<SLUResult> combineResults = new ArrayList<>();
        for (String key : resultMap.keySet()){
            String name = key.split("@")[1];
            String type = key.split("@")[0];
            Double score = resultMap.get(key);
            if (gateResult.size() > 0) {//若有Gate结果，一般情况下只有一个##和一个以上的slot
                combineResults.add(new SLUResult(name, type, score, gateResult.get(0).slots));//type需要判断
            }else {//若Gate未找到slot
                combineResults.add(new SLUResult(name, type, score, new HashMap<>()));
            }
        }
        return combineResults;

    }

    public SysAction keypadParser(String keypad, String userid, String telephoneNumber) throws Exception {
        try {
            //Gate识别
            ArrayList<SLUResult> gateResult = gateAnalyser(keypad);
            currentSLU = gateResult;
            logger.error("\n" + "==================" + "\n"
                    + "query: " + keypad + "\n" + "sluResul: "+ currentSLU.toString() + "\n"
                    + "==================");
            //currentSLU是理解结果,送入对话管理器
            currentAction = parseAction(currentSLU, userid, keypad, telephoneNumber);
            //cleaning
        } catch (ResourceInstantiationException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
        return currentAction;
    }

    public SysAction queryParser(String input, String userid, String telephoneNumber) throws Exception {
        try {
            if (input.trim().length()>0 ) {//Input不为空
                //step1 预处理：替换掉荷包这类情况
                String inputPrepro = preprocess(input);
                //step2 Gate 解析slot
                ArrayList<SLUResult> gateResult = gateAnalyser(inputPrepro);
                //step3 获取分类标签，通过两种方式：1 相似度计算（编辑距离）； 2分类器
                ArrayList<SLUResult> combineResults;
                Map<String, Double> outTermResults = outOfVacProcess(inputPrepro);
                if (outTermResults.size() > 0) {//得到匹配结果，和Gate相结合
                    combineResults = combineResult(outTermResults, gateResult);
                    currentSLU = combineResults;
                } else { //编辑距离没有结果，进入分类器
                    Map<String, Double> classifierResultMap = new HashMap<>();
                    List<ClassifierResult> classifierResults = classifier.predict(inputPrepro);//获取分类器结果
                    if (classifierResults.size() > 0) { //分类器有结果
                        for (ClassifierResult c : classifierResults) {
                            classifierResultMap.put(c.getAnswer(), c.getScore());
                        }
                        combineResults = combineResult(classifierResultMap, gateResult);
                        currentSLU = combineResults;
                    } else {//若没有分类器结果，直接采用gate结果
                        currentSLU = gateResult;
                    }
                }
            } else {//input为空直接返回空的currentSLU
                currentSLU = new ArrayList<>();
            }
            logger.error("\n" + "==================" + "\n"
                    + "query: " + input + "\n" + "sluResul: "+ currentSLU.toString() + "\n"
                    + "==================");

            long start1,end1;
            start1 = System.currentTimeMillis();
            //currentSLU是理解结果,送入对话管理器
            currentAction = parseAction(currentSLU, userid, input, telephoneNumber);
            end1 = System.currentTimeMillis();
            System.out.println("-----------对话时间-------start time:" + start1+ "; end time:" + end1+ "; " +
                    "Run Time:" + (end1 - start1) + "(ms)");
            //cleaning
        } catch (ResourceInstantiationException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
        return currentAction;
    }

    //获取Gate标注结果
    public ArrayList<SLUResult> getSLUResult(AnnotationSet annotationSet){

        ArrayList<SLUResult> res = new ArrayList<>();
        double maxEffectiveLength = 0.0;
        for (Annotation as : annotationSet) {
            double curEffectiveLength = (double) as.getFeatures().get("effective_length");
            if(curEffectiveLength>maxEffectiveLength)
                maxEffectiveLength = curEffectiveLength;
        }
        Set<Annotation> setAnnotationFilterbyLength = new HashSet<>();
        for (Annotation as : annotationSet) {
            double curEffectiveLength = (double) as.getFeatures().get("effective_length");
            if(Math.abs(curEffectiveLength-maxEffectiveLength)<1e-20)
                setAnnotationFilterbyLength.add(as);//只保留最大的maxEffectiveLength
        }
        int minSlotNum = Integer.MAX_VALUE;
        for (Annotation i : setAnnotationFilterbyLength) {
            int curSlotNum = (int) i.getFeatures().get("slotNum");
            if(curSlotNum<minSlotNum)
                minSlotNum = curSlotNum;
        }
        Set<Annotation> setAnnotationFilterbySlotNum = new HashSet<>();
        for (Annotation i : setAnnotationFilterbyLength) {
            int curSlotNum = (int) i.getFeatures().get("slotNum");
            if(curSlotNum==minSlotNum)
                setAnnotationFilterbySlotNum.add(i);
        }//在maxEffectiveLength相等的情况下保留minSlotNum最少的

        conflictReply = checkVariable(setAnnotationFilterbySlotNum);//若筛选后还有多个，合成多个候选回复

        Set<SLUResult> matchSluResults = new HashSet<>();
        Set<SLUResult> noMatchSluResults = new HashSet<>();

        for (Annotation i : setAnnotationFilterbySlotNum) {
            String ruleName = (String) i.getFeatures().get("rule");
            String ruleType = (String) i.getFeatures().get("type");
            double curEffectiveLength = ((double) i.getFeatures().get("effective_length"));
            FeatureMap oriFeatures = (FeatureMap) i.getFeatures().get("slots");

            boolean slotMatch = true;

            //默认赋值函数的功能是？
            if(defaultFunction!=null){
                //oriFeatures.putAll(defaultFunction.assignRelatedVal(oriFeatures));
                FeatureMap featureMapAdd = defaultFunction.assignRelatedVal(oriFeatures);
                for (Object key:featureMapAdd.keySet()){
                    if(!oriFeatures.containsKey(key))
                        oriFeatures.put(key,featureMapAdd.get(key));
                    else if(!((FeatureMap)oriFeatures.get(key)).get("抽取名").
                            equals(((FeatureMap)featureMapAdd.get(key)).get("抽取名")))
                        slotMatch = false;
                }
            }

            Map<String,String> slots = new HashMap<>();

            for (Object key:oriFeatures.keySet()){
                String key1 = key.toString().replace("$","");
                slots.put(key1,(String) ((FeatureMap)oriFeatures.get(key)).get("抽取名"));
            }
            if(slotMatch)
                matchSluResults.add(new SLUResult(ruleName, ruleType, curEffectiveLength, slots));
            else
                noMatchSluResults.add(new SLUResult(ruleName, ruleType, curEffectiveLength, slots));
        }
        if(matchSluResults.isEmpty())
            res.addAll(noMatchSluResults);
        else
            res.addAll(matchSluResults);

        return res;
    }

    private static String JoinStr(Collection<String> strlist, String sep){
        String str = "";

        for (String string :strlist){
            str+=string;
            str+=sep;
        }

        if(strlist.size()>0){
            str=str.substring(0,str.length()-sep.length());
        }
        return str;
    }

    private String checkVariable(Set<Annotation> filterSet){

        String str = "";
        Map<String,Set<String>> varMap = new HashMap<>();

        for (Annotation i:filterSet){
            String action = (String) i.getFeatures().get("command");
            FeatureMap oriFeatures = (FeatureMap) i.getFeatures().get("slots");
            for (Object key:oriFeatures.keySet()){
                String str_userinput = (String) ((FeatureMap)oriFeatures.get(key)).get("matchedString");
                String str_standardName = (String) ((FeatureMap)oriFeatures.get(key)).get("抽取名");
                Set<String> stringList = varMap.get(str_userinput);
                if(stringList==null){
                    stringList = new HashSet<>();
                    varMap.put(str_userinput,stringList);
                }
                stringList.add(str_standardName);
            }
        }

        for (String str_userinput:varMap.keySet()){
            if(varMap.get(str_userinput).size()>1){
                str=str_userinput+"可能是";
                String strJoin = JoinStr(varMap.get(str_userinput),"、");
                str+=strJoin;
                str+="，请问您要选择哪一个？";
                break;
            }
        }
        return str;
    }


    //接收Gate标注结果+userid送入对话管理器
    public SysAction parseAction(ArrayList<SLUResult> res,String userid, String input, String telephoneNumber){


        String note = "";//input正常，as正常，无note
        if (input.trim().equals("")){
            note = "noInput";

        }else if (res == null || res.size() == 0){
            note = "noMatch";
        }
        SysAction ac = null;
        try {
            ac = dialogManager.feedUserInput(userid, res, note,conflictReply, telephoneNumber);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (note.equals("") && ac == null) {
                System.out.println("对话管理器失败！ ");
            }
        }
        return ac;
    }

    //过滤掉slulist和最大值相差太多的元素
    private ArrayList<SLUResult> filterMaxSLUList(ArrayList<SLUResult> slulist) {

        if (slulist == null || slulist.isEmpty()) {
            return slulist;
        }
        slulist = filterSLUList(slulist);
        SLUResult highScore = null;
        ArrayList<SLUResult> newslulist = new ArrayList<>();
        for (SLUResult slu : slulist) {
            if (highScore == null || highScore.score < slu.score) {
                highScore = slu;
            }
        }
        for (SLUResult slu : slulist){
            if(Math.abs(slu.score - highScore.score)<1e-20){
                newslulist.add(slu);
            }
        }
        return newslulist;
    }

    //取slulist里面slu.score最大分数重组为slulist
    private ArrayList<SLUResult> filterSLUList(ArrayList<SLUResult> slulist){

        Map<String,SLUResult> sluMap = new HashMap<>();
        ArrayList<SLUResult> newslulist = new ArrayList<>();

        for (SLUResult slu : slulist){
            SLUResult oldslu = sluMap.get(slu.stateId);
            if (oldslu==null || oldslu.score<slu.score){
                sluMap.put(slu.stateId, slu);
            }
        }
        for (String str : sluMap.keySet()){
            newslulist.add(sluMap.get(str));
        }
        return newslulist;
    }

    public SerialAnalyserController getAnalyser(){
        return this.analyser;
    }

    public void setAnalyser(SerialAnalyserController analyser){
        this.analyser = analyser;
    }

    public ArrayList<SLUResult> getCurrentSLU() {
        return currentSLU;
    }

    public void setCurrentSLU(ArrayList<SLUResult> currentSLU) {
        this.currentSLU = currentSLU;
    }

    public SysAction getCurrentAction() {
        return currentAction;
    }

    public void setCurrentAction(SysAction currentAction) {
        this.currentAction = currentAction;
    }

    public DialogManager getDialogManager() {
        return dialogManager;
    }

    public void setDialogManager(DialogManager dialogManager) {
        this.dialogManager = dialogManager;
    }

    public String getConflictReply() {
        return conflictReply;
    }

    public void setConflictReply(String conflictReply) {
        this.conflictReply = conflictReply;
    }

    public DefaultFunction getDefaultFunction() {
        return defaultFunction;
    }

    public void setDefaultFunction(DefaultFunction defaultFunction) {
        this.defaultFunction = defaultFunction;
    }

    private static List<String> generateSlotInput(Parser parser, String nodeName, StateMachine machine){

        List<String> matchSlots = new ArrayList<>();

        Set<Annotation> gazSet = parser.getCurrentGaz();

        Set<String> params = new HashSet<>();
        params.addAll(machine.getStateFormID(nodeName).getParams());


        Map<String, List<Annotation>> gazMap = new HashMap<>();

        for (Annotation annotation:gazSet){
            String match = (String) annotation.getFeatures().get("MatchedString");
            List<Annotation> matchSet = gazMap.get(match);
            if(matchSet==null){
                matchSet = new ArrayList<>();
                gazMap.put(match,matchSet);
            }
            matchSet.add(annotation);
        }

        for (String match:gazMap.keySet()){
            List<Annotation> matchSet = gazMap.get(match);

            if(matchSet.size()>1){
                List<String> matchList = new ArrayList<>();
                List<String> matchSetString = new ArrayList<>();

                for (Annotation as:matchSet){
                    if(params.contains(as.getType()))
                        matchList.add(as.getType());
                    matchSetString.add(as.getType());
                }

                if(matchList.size()==1)
                    matchSlots.add(matchList.get(0));
                else {

                    matchSlots.add("("+JoinStr(matchSetString,"|")+")");
                }


            }
            else
                matchSlots.add(matchSet.get(0).getType());


        }

        return matchSlots;


    }
/*    private void maxTest() throws Exception {

        String line = "八十八元的";
        List<ClassifierResult> classifierResults = classifier.predict(line);//获取分类器结果
        if (classifierResults.size()>0) {
            for (ClassifierResult c : classifierResults) {
                System.out.println(c.getAnswer()+c.getScore());
            }
        }
    }*/

    private  static void sluTest() throws Exception {

        MongoDB.setDBConf("nxydNew", "localhost", "", "", 27017);

        String home = Parser.class.getClassLoader().getResource(".").getPath();
        Gate.setGateHome(new File(home));
        try {
            Gate.init();
            Gate.getCreoleRegister().registerDirectories(ParseServer.class.getClassLoader().getResource("plugins/ANNIE"));
        } catch (GateException e) {
            e.printStackTrace();
        }

        System.out.println("Preparing parser pool...");
        URL appURL = ParseServer.class.getClassLoader().getResource("plugins/anguang.gapp");

        Parser parser = new Parser(appURL);
//        parser.maxTest();
        parser.processer.index(Parser.class.getClassLoader().getResource("corpus").getPath());
        String userid = "test";
        String query;  //批量赋值给query
        try {
            String outpath = ParseServer.class.getClassLoader().getResource("sluTestResult.txt").getPath();
            OutputStreamWriter fw = new OutputStreamWriter(new FileOutputStream(outpath, false),"UTF-8");

            String path = ParseServer.class.getClassLoader().getResource("totalTestQuery1.txt").getPath();
            InputStreamReader read = new InputStreamReader(new FileInputStream(path), "UTF-8");
            BufferedReader reader = new BufferedReader(read);
            String line = "";
            while ((line = reader.readLine()) != null) {
                query = line.trim().split("\t")[0].replace(" ","").toLowerCase();
                String gold = line.trim().split("\t")[1];
                parser.queryParser(query, userid, "1");

                //观察slu结果
                ArrayList<SLUResult> sluResults = parser.getCurrentSLU();
                fw.write(query + "\t" + sluResults.toString() + "\t" + gold + "\n");

/*                List<String> resls = new ArrayList<>();
                for (SLUResult res : sluResults) {
                    resls.add(res.ruleName);
                }
                if (!resls.contains(gold)) {
                    System.out.println(query+"\t"+sluResults.toString()+"\t"+gold);
//                    fw.write(query+"\t"+sluResults.toString()+"\t"+gold+"\n");
                }

//
                if (sluResults.size()> 1) {
                    fw.write(query + "\t" + sluResults.toString() + "\t" + gold + "\n");
                }*/
            }
            fw.close();
            reader.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }


    private  static void testGrammar() throws Exception {

        MongoDB.setDBConf("nxydNew", "localhost", "", "", 27017);

        String home = Parser.class.getClassLoader().getResource(".").getPath();
        Gate.setGateHome(new File(home));
        try {
            Gate.init();
            Gate.getCreoleRegister().registerDirectories(ParseServer.class.getClassLoader().getResource("plugins/ANNIE"));
        } catch (GateException e) {
            e.printStackTrace();
        }

        StateMachineModel smm = StateMachineModel.getModelFromInternelJson("plugins/nodeInfo.json");
        DialogManager dialogManager = new DialogManager(60,true);//60s不运行则超时，重置状态机
        dialogManager.setStateMachineModel(smm);
        dialogManager.setConfig(true, "http://192.168.2.222:8080/hello/",5000);

        System.out.println("Preparing parser pool...");
        URL appURL = ParseServer.class.getClassLoader().getResource("plugins/anguang.gapp");

        Parser parser = new Parser(appURL);
        parser.processer.index(Parser.class.getClassLoader().getResource("corpus").getPath());
//        parser.setDialogManager(dialogManager);
        String userid = "test";
        String query;  //批量赋值给query
        String telephoneNumber;  //批量赋值给query
        try {
            String outpath = ParseServer.class.getClassLoader().getResource("TestResult.txt").getPath();
            OutputStreamWriter fw = new OutputStreamWriter(new FileOutputStream(outpath, false),"UTF-8");

            String path = ParseServer.class.getClassLoader().getResource("Test.txt").getPath();
            InputStreamReader read = new InputStreamReader(new FileInputStream(path), "UTF-8");
            BufferedReader reader = new BufferedReader(read);
            String line = "";
            while ((line = reader.readLine()) != null) {
                query = line.trim().split("\t")[0];
                telephoneNumber = line.trim().split("\t")[1];
                String gold = line.trim().split("\t")[2];
                parser.queryParser(query, userid, telephoneNumber);


                //观察slu结果
                ArrayList<SLUResult> sluResults = parser.getCurrentSLU();
                List<String> resls = new ArrayList<>();
                for (SLUResult res : sluResults) {
                    resls.add(res.ruleName);
                }
                if (!resls.contains(gold)) {
                    System.out.println(line+"\t"+sluResults.toString());
                    fw.write(line+"\t"+sluResults.toString()+"\n");
                }

                //观察dm结果
                SysAction action = parser.getCurrentAction();
                System.out.println(action);

            }
            fw.close();
            reader.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }


    public static void main(String[] args) throws Exception {

        sluTest();
    }
}
